# ~*~ coding: utf-8 ~*~
import time
import selectors

from apps.extensions import db
from .recorder import get_recorder
from ..models import Session as SessionEntity

BUF_SIZE = 4096


class Session(object):
    sessions = {}

    def __init__(self, client, server):
        self.sid = client.id
        self.client = client  # Client channel
        self.server = server  # Server channel

        self.is_finished = False
        self.sel = selectors.DefaultSelector()
        self.recorder = None

        self.date_start = time.time()
        self.date_end = None

        self.timeout = 60*60*24

    @classmethod
    def new_session(cls, client, server):
        session = cls(client, server)
        session.set_recorder(recorder=get_recorder(session))
        cls.sessions[session.sid] = session
        SessionEntity.create_from_dict(session.to_json())
        return session

    @classmethod
    def get_session(cls, sid):
        return cls.sessions.get(sid)

    @classmethod
    def remove_session(cls, sid):
        session = cls.get_session(sid)
        if session:
            session.close()
            session.date_end = time.time()
            cls.sessions.pop(sid, None)
            session_entity = db.session.query(SessionEntity).get(session.sid)
            session_entity.update(**session.to_json())

    def set_recorder(self, recorder):
        self.recorder = recorder
        self.recorder = recorder

    def close(self):
        self.is_finished = True

    @property
    def closed_unexpected(self):
        return not self.is_finished and (self.client.closed or self.server.closed)

    def set_timeout(self, timeout):
        self.timeout = timeout

    def bridge(self):
        """
        Bridge clients with service
        :return:
        """
        self.recorder.start()

        self.sel.register(self.client, selectors.EVENT_READ)
        self.sel.register(self.server, selectors.EVENT_READ)

        start_session = time.time()
        while not self.is_finished:
            if start_session + self.timeout < time.time():
                break

            events = self.sel.select(timeout=60)
            if self.client.closed:
                break
            if self.server.closed:
                break
            for sock in [key.fileobj for key, _ in events]:
                data = sock.recv(BUF_SIZE)
                if len(data) > 0:
                    start_session = time.time()

                if sock == self.server:
                    if len(data) == 0:
                        print("Server close the connection")
                        self.is_finished = True
                        break
                    self.client.send(data)
                    self.recorder.record([time.time() - self.date_start, 'o', bytes.decode(data)])
                elif sock == self.client:
                    if len(data) == 0:
                        print("Client close the connection: {}".format(self.client))
                        self.is_finished = True
                        break
                    self.server.send(data)

        self.is_finished = True
        self.client.send(b'\r\nlogout')
        self.recorder.stop([time.time() - self.date_start, 'o', '\r\nlogout'])

    def to_json(self):
        return {
            "id": self.sid,
            "user": "{} ({})".format(self.client.user.name,
                                     self.client.user.username),
            "asset": "{} ({})".format(self.server.asset.hostname,
                                      self.server.asset.manage_ip),
            "system_user": self.server.system_user.username,
            "login_from": self.client.login_from,
            "remote_addr": self.client.addr[0],
            "is_finished": self.is_finished,
            "date_start": self.date_start,
            "date_end": self.date_end if self.date_end else None,
            "record_path": self.recorder.record_path
        }
